// 
//   SubSonic - http://subsonicproject.com
// 
//   The contents of this file are subject to the New BSD
//   License (the "License"); you may not use this file
//   except in compliance with the License. You may obtain a copy of
//   the License at http://www.opensource.org/licenses/bsd-license.php
//  
//   Software distributed under the License is distributed on an 
//   "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
//   implied. See the License for the specific language governing
//   rights and limitations under the License.
// 
using System;
using System.Globalization;
using System.Security.Cryptography;
using System.Text.RegularExpressions;

namespace MVCPets.Extensions
{
  /// <summary>
  /// Summary for the Numbers class
  /// </summary>
  public static class Numeric
  {
    /// <summary>
    /// Determines whether a number is a natural number (positive, non-decimal)
    /// </summary>
    /// <param name="sItem">The s item.</param>
    /// <returns>
    /// 	<c>true</c> if [is natural number] [the specified s item]; otherwise, <c>false</c>.
    /// </returns>
    public static bool IsNaturalNumber(this string sItem)
    {
      Regex notNaturalPattern = new Regex("[^0-9]");
      Regex naturalPattern = new Regex("0*[1-9][0-9]*");

      return !notNaturalPattern.IsMatch(sItem) && naturalPattern.IsMatch(sItem);
    }

    /// <summary>
    /// Determines whether [is whole number] [the specified s item].
    /// </summary>
    /// <param name="sItem">The s item.</param>
    /// <returns>
    /// 	<c>true</c> if [is whole number] [the specified s item]; otherwise, <c>false</c>.
    /// </returns>
    public static bool IsWholeNumber(this string sItem)
    {
      Regex notWholePattern = new Regex("[^0-9]");
      return !notWholePattern.IsMatch(sItem);
    }

    /// <summary>
    /// Determines whether the specified s item is integer.
    /// </summary>
    /// <param name="sItem">The s item.</param>
    /// <returns>
    /// 	<c>true</c> if the specified s item is integer; otherwise, <c>false</c>.
    /// </returns>
    public static bool IsInteger(this string sItem)
    {
      Regex notIntPattern = new Regex("[^0-9-]");
      Regex intPattern = new Regex("^-[0-9]+$|^[0-9]+$");

      return !notIntPattern.IsMatch(sItem) && intPattern.IsMatch(sItem);
    }

    /// <summary>
    /// Determines whether the specified s item is number.
    /// </summary>
    /// <param name="sItem">The s item.</param>
    /// <returns>
    /// 	<c>true</c> if the specified s item is number; otherwise, <c>false</c>.
    /// </returns>
    public static bool IsNumber(this string sItem)
    {
      double result;
      return (double.TryParse(sItem, NumberStyles.Float, NumberFormatInfo.CurrentInfo, out result));
    }

    /// <summary>
    /// Determines whether the specified value is an even number.
    /// </summary>
    /// <param name="value">The value.</param>
    /// <returns>
    /// 	<c>true</c> if the specified value is even; otherwise, <c>false</c>.
    /// </returns>
    public static bool IsEven(this int value)
    {
      return ((value & 1) == 0);
    }

    /// <summary>
    /// Determines whether the specified value is an odd number.
    /// </summary>
    /// <param name="value">The value.</param>
    /// <returns>
    /// 	<c>true</c> if the specified value is odd; otherwise, <c>false</c>.
    /// </returns>
    public static bool IsOdd(this int value)
    {
      return ((value & 1) == 1);
    }

    /// <summary>
    /// Determines if an int is between the other two ints
    /// </summary>
    /// <param name="value">the int to look at</param>
    /// <param name="low">the lower boundary</param>
    /// <param name="high">the upper boundary</param>
    /// <returns></returns>
    public static bool IsBetween(this int value, int low, int high, BetweenOptions options)
    {
      bool ret = false;

      if (options == BetweenOptions.BetweenOnly) ret = value > low && value < high;
      else if (options == BetweenOptions.IncludeBoth) ret = value >= low && value <= high;
      else if (options == BetweenOptions.IncludeLower) ret = value >= low && value < high;
      else if (options == BetweenOptions.IncludeHigher) ret = value > low && value <= high;

      return ret;
    }

    /// <summary>
    /// Determines if an int is between the other two ints
    /// </summary>
    /// <param name="value">the int to look at</param>
    /// <param name="low">the lower boundary</param>
    /// <param name="high">the upper boundary</param>
    /// <returns></returns>
    public static bool IsBetween(this int value, int low, int high)
    {
      return IsBetween(value, low, high, BetweenOptions.BetweenOnly);
    }

    /// <summary>
    /// Generates a random number with an upper bound
    /// </summary>
    /// <param name="high">The high.</param>
    /// <returns></returns>
    public static int Random(this int high)
    {
      byte[] random = new Byte[4];
      new RNGCryptoServiceProvider().GetBytes(random);
      int randomNumber = BitConverter.ToInt32(random, 0);

      return Math.Abs(randomNumber % high);
    }

    /// <summary>
    /// Generates a random number between the specified bounds
    /// </summary>
    /// <param name="low">The low.</param>
    /// <param name="high">The high.</param>
    /// <returns></returns>
    public static int Random(this int low, int high)
    {
      return new Random().Next(low, high);
    }

    public static char Character(this int value)
    {
      return (char)value;
    }

    /// <summary>
    /// Converts the integer to the Excel date representation.
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public static DateTime ToExcelDate(this int value)
    {
      // Excel messed up, 1900 was not a leap year but they consider it so
      if (value > 60) value--;

      return "1/1/1900".Parse<DateTime>().AddDays(--value);
    }
  }
}